package com.condroid;

import java.util.LinkedList;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import ioio.lib.api.IOIO;
import ioio.lib.api.IOIOFactory;
import ioio.lib.api.Uart;
import ioio.lib.api.exception.ConnectionLostException;
import ioio.lib.api.exception.IncompatibilityException;

/**
 * This class is used to establish and maintain IOIO board
 * connection. For each instant of this application, there
 * will only one instant of this class. Use static method
 * 'getInstance' to get it. programmer can not use the 
 * constructor of this class which is private in order to 
 * prevent generate more than one instant of this class.
 * Refer to AbstractIOIOActivity class which provided by IOIO.
 * @author JUN GAO
 * @version 1.0
 * Condroid Summer CSD 2011 - KTH
 */
public class IOIOThread extends Thread {
	private static IOIOThread currentThread =null;
	private static IOIO ioio = null;
	private static LinkedList<Uart> uartList = new LinkedList<Uart>();//store the Uart in use
	private static LinkedList<UartInfo> uartInfoList = new LinkedList<UartInfo>();//store the in use Uart info 
	private static boolean abort = false; // ioio disconnected
	private static boolean connected = false;//the connection state of ioio
	private Uart uartOpened = null;//the Uart channel just opened
	private Uart uartNeedToClose = null;//the Uart channel need to be closed
	private UartInfo connectInfo = null;//the Uart info for opening
	private UartInfo disconnectInfo = null;//the Uart info for closing
	private Handler handler = null;


	private IOIOThread(){
		currentThread = this;
		setDaemon(true);
	}
	
	/**
	 * Use this to get the only one IOIOThread.
	 * @return The IOIOThread of this application instant.
	 */
	public static synchronized IOIOThread getInstance(){
		if(currentThread == null)
			currentThread = new IOIOThread();
		return currentThread;
	}
	
	/**
	 * run() will work in a loop and is going to generate 
	 * and monitor the ioio instant state, keep it connect
	 * to the board. Secondly, it will setup and close Uart 
	 * channels on the demand
	 */
	
	@Override
	public void run() {
		super.run();
		while (true) {
			try {
				synchronized (this) {
					if (abort)
						break;
					ioio = IOIOFactory.create();
				}
				ioio.waitForConnect();
				connected = true;
				while (!abort) {
					setup();
					close();
					sleep(100);
				}
			} catch (ConnectionLostException ce) {
				if (abort)
					break;
				else {
					errorReport("IOIOThread run() waitForConnect() or setup() ConnectionLostException!"
							+ ConstantData.NEWLINE);
					connected = false;
				}
			} catch (IncompatibilityException ie) {
				errorReport("IOIOThread run() waitForConnect() IncompatibilityException!"
						+ ConstantData.NEWLINE);
				ioio.disconnect();
				connected = false;
			} catch (InterruptedException ie) {
				errorReport("IOIOThread run() sleep() InterruptedException!"
						+ ConstantData.NEWLINE);
				ioio.disconnect();
				connected = false;
			} catch (Exception e) {
				errorReport("IOIOThread run() Other exception!"
						+ ConstantData.NEWLINE);
				ioio.disconnect();
				connected = false;
			} finally {
				try {
					ioio.waitForDisconnect();
					connected = false;
				} catch (InterruptedException ie) {
					errorReport("IOIOThread run() waitForDisconnection() InterruptedException!"
							+ ConstantData.NEWLINE);
				}
			}
		}
	}

	/*setup the Uart channels*/
	private synchronized void setup() throws ConnectionLostException {
		if (connectInfo != null) {
				uartOpened = ioio.openUart(connectInfo.getRxPin(),
						connectInfo.getTxPin(), connectInfo.getBaud(),
						connectInfo.getParity(), connectInfo.getStopBits());
				uartInfoList.add(connectInfo);
				uartList.add(uartOpened);
				connectInfo = null;
				notify();
			}
	}

	/*close the Uart channels which already opened*/
	private void close()throws ConnectionLostException{
		if(uartNeedToClose != null){
			uartList.remove(uartNeedToClose);
			uartInfoList.remove(disconnectInfo);
			uartNeedToClose.close();
			uartNeedToClose = null;
			disconnectInfo = null;
		}
	}

	/**
	 * give the informations which will be used for
	 * setup the Uart channels.
	 * @param connectInfo The UartInfo include the Uart parameter
	 * @return if true, the parameter is OK or is not.
	 */
	
	public synchronized boolean openUart(UartInfo connectInfo){
		if (!uartInfoList.isEmpty()) {
			for(UartInfo u : uartInfoList){
				if(u.hasSamePin(connectInfo))
					return false;
			}
//			UartInfo[] ua = (UartInfo[]) uartInfoList.toArray();
//			int i;
//			for (i = 0; i < ua.length; i++)
//				if (ua[i].hasSamePin(connectInfo))
//					return false;
		}
		this.connectInfo = connectInfo;
		
		uartOpened = null;
		while(uartOpened == null){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * give the Uart instant and its info to close
	 * the channel. Channels need to be closed before reuse
	 * @param uart the Uart need to be closed.
	 * @param disconnectInfo the UartInfo of the Uart.
	 * @return true, the channel will be closed or the parameters
	 * are not correct.
	 */
	
	public synchronized boolean closeUart(Uart uart, UartInfo disconnectInfo){
		if(uartList.contains(uart)){
			uartNeedToClose = uart;
			this.disconnectInfo = disconnectInfo;
			return true;
		}else
			return false;
	}
	
	/**
	 * use this method immediately after open to 
	 * get the Uart channel.
	 * @return the Uart instant generated.
	 * @see #openUart(UartInfo)
	 */
	
	public Uart getUart(){
		return uartOpened;
	}
	
	/**
	 * get the connection state of ioio. Programmer need
	 * to make sure it is connected before {@link #openUart(UartInfo)}
	 * and {@link #closeUart(Uart, UartInfo)}
	 * @return true if connected or will return false.
	 */
	public boolean isConnected(){
		return connected;
	}
	
	/**
	 * set the handler for display errors happened 
	 * in this thread. When changing display channels,
	 * this need to be modified. Set it as null to 
	 * neglect the error information.
	 * @param handler the handler used to display errors.
	 */
	public synchronized void setErrorReportHandler(Handler handler){
		this.handler = handler;
	}
	
	private void errorReport(String s){
		if(handler != null){
			Message m = Message.obtain();
			m.setTarget(handler);
			m.obj = s;
			m.sendToTarget();
		}
	}
	
	/**
	 * when software is going to close. make sure
	 * to close the ioio by using this meth
	 */
	public synchronized static void abort() {
		abort = true;
		if (ioio != null) {
			ioio.disconnect();
			connected = false;
		}
	}
}
